//-----------------------------------------------------------------------
// <copyright file="TupleRelation.cs" company="tuliprules.org">
//     Copyright (c) 2010 tuplelister, tuliprules.org.
// </copyright>
// <summary>This is the TupleRelation class.</summary>
//-----------------------------------------------------------------------

namespace Tulip.Engine.Tuples
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using Exceptions;

#pragma warning disable 1587
    /// <summary>
    /// Delegate.   Used to evaluate tuple members.
    /// </summary>
    /// <typeparam name="TTuple">A tuple type.</typeparam>
    /// <param name="tuple">The tuple which contains a member to be evaluated.</param>
    /// <returns>An IComparable value.</returns>
#if MONO201_COMPLIANT
    public delegate IComparable Evaluate<TTuple>(TTuple tuple);
#else
    public delegate IComparable Evaluate<in TTuple>(TTuple tuple) where TTuple : Tuple;
#endif
#pragma warning restore 1587
    /// <summary>
    /// Delegate.   Used to return a stringly-typed tuple view dictionary.
    /// </summary>
    /// <returns>A strongly-typed tuple view.</returns>
    public delegate IDictionary TupleViewFactory();

#pragma warning disable 1587
    /// <summary>
    /// Class.   Represents a group of tuple views.   All tuples referenced in a tuple relation
    /// are of the same type.
    /// </summary>
    /// <typeparam name="TTuple">The type of tuple that will be referenced in the tuple relation.</typeparam>
#if MONO201_COMPLIANT
    public class TupleRelation<TTuple> : ITupleRelation<TTuple> where TTuple : class
#else
    public class TupleRelation<TTuple> : ITupleRelation<TTuple> where TTuple : Tuple
#endif
#pragma warning restore 1587
    {
        /// <summary>
        /// Collection of sorted dictionaries.   Each sorted dictionary contains lists of tuple references.
        /// </summary>
        private readonly Dictionary<TupleIndexer<TTuple>, IDictionary> tupleRelation = new Dictionary<TupleIndexer<TTuple>, IDictionary>();

        /// <summary>
        /// Set of registered evaluators.
        /// </summary>
        private readonly HashSet<TupleIndexer<TTuple>> tupleIndexers = new HashSet<TupleIndexer<TTuple>>();

        /// <summary>
        /// Key value used as key for 'all' indexer.
        /// </summary>
        private static readonly Type AllKey = typeof(All);

        /// <summary>
        /// Indicates if the tuple relation has added tuples to the special 'all' group.
        /// </summary>
        private bool addAll;

        /// <summary>
        /// A tuple indexer that references all tuples in the relation.
        /// </summary>
        private TupleIndexer<TTuple> evaluateAll;

        /// <summary>
        /// Initializes a new instance of the TupleRelation class.
        /// </summary>
        /// <param name="id">The unique id for the tuple relation.</param>
        public TupleRelation(int id)
        {
            this.Id = id;
        }

        /// <summary>
        /// Gets the identity of the tuple relation.   The identity is a unique integer value.
        /// </summary>
        public int Id { get; private set; }

        /// <summary>
        /// Returns a Tuple View from the group.
        /// </summary>
        /// <typeparam name="TKey">The view's key type.</typeparam>
        /// <param name="tupleIndexer">The tuple indexer used to select the view.</param>
        /// <returns>A dictionary containing a tuple view.</returns>
        public Dictionary<TKey, TupleLinkedList> GetTupleView<TKey>(TupleIndexer<TTuple> tupleIndexer)
        {
            Debug.Assert(tupleIndexer != null, "The indexer delegate cannot be null");

            return this.tupleRelation[tupleIndexer] as Dictionary<TKey, TupleLinkedList>;
        }

        /// <summary>
        /// Returns a Tuple View from the group.
        /// </summary>
        /// <param name="tupleIndexer">An evaluator delegate.</param>
        /// <returns>
        /// A tuple view consisting of a sorted list of lists of tuples.   The tuple view is 
        /// sorted by values returned from the delegate.
        /// </returns>
        /// <remarks>
        /// A Tuple View is a sorted list of lists of tuples.   All tuples are of a given type.
        /// The lists of tuples are sorted by values returned by a Tuple View evaluator.    The 
        /// evaluator is used as the key value for tuple views in order to store and retrieve them
        /// from the tuple relation. 
        /// </remarks>
        public IDictionary GetTupleView(TupleIndexer<TTuple> tupleIndexer)
        {
            Debug.Assert(tupleIndexer != null, "The indexer delegate cannot be null");

            return this.tupleRelation[tupleIndexer];
        }

        /// <summary>
        /// Returns a Tuple View from the group for all tuples.
        /// </summary>
        /// <typeparam name="TKey">The key value.</typeparam>
        /// <returns>
        /// A tuple view consisting of a dictionary of lists of tuples.   The tuple view list
        /// contains a single list containing all tuples.
        /// </returns>
        /// <remarks>
        /// A Tuple View is a dictionary of lists of tuples.   All tuples are of a given type.
        /// In this case, the inner list contains all tuples of the given type.
        /// </remarks>
        public Dictionary<TKey, TupleLinkedList> GetTupleView<TKey>()
        {
            var tupleViewOut = default(Dictionary<TKey, TupleLinkedList>);

            if (this.addAll)
            {
                tupleViewOut = this.tupleRelation[this.evaluateAll] as Dictionary<TKey, TupleLinkedList>;
            }

            return tupleViewOut ?? new Dictionary<TKey, TupleLinkedList>();
        }

        /// <summary>
        /// Returns a tuple list from a tuple view.
        /// </summary>
        /// <param name="tupleIndexer">The tuple indexer used to select a view.</param>
        /// <param name="keyValue">A key value used for  match.</param>
        /// <typeparam name="TKey">The key type.</typeparam>
        /// <returns>
        /// A tuple list contains tuples that match a given key value. 
        /// </returns>
        /// <remarks>
        /// A tuple list is contains all tuples of the given type that meet a given criteria.
        /// </remarks>
        public TupleLinkedList GetTupleList<TKey>(TupleIndexer<TTuple> tupleIndexer, TKey keyValue)
        {
            TupleLinkedList list;

            if (tupleIndexer != null && GetTupleView<TKey>(tupleIndexer).TryGetValue(keyValue, out list))
            {
                return list;
            }

            // Return a 'dummy' empty linked list.   The indexId is not relevant.
            return TupleLinkedListPool.GetItem(0);
        }

        /// <summary>
        /// Returns a list of tuples from a tuple view that match the given key.
        /// </summary>
        /// <typeparam name="TKey">The key type.</typeparam>
        /// <param name="tupleView">The tuple view.</param>
        /// <param name="keyValue">The key value.</param>
        /// <returns>A linked list of tuples that match the key value.</returns>
        public TupleLinkedList GetTupleList<TKey>(IDictionary tupleView, TKey keyValue)
        {
            TupleLinkedList list;

            return tupleView != null && ((Dictionary<TKey, TupleLinkedList>)tupleView).TryGetValue(keyValue, out list)
                       ? list
                       : TupleLinkedListPool.GetItem(0);
        }

        /// <summary>
        /// Returns a tuple list from the 'all' tuple view.
        /// </summary>
        /// <param name="keyValue">A key value used for  match.</param>
        /// <typeparam name="TKey">The key type.</typeparam>
        /// <returns>
        /// A tuple list contains tuples that match a given key value. 
        /// </returns>
        /// <remarks>
        /// A tuple list is contains all tuples of the given type that meet a given criteria.
        /// </remarks>
        public TupleLinkedList GetTupleList<TKey>(TKey keyValue)
        {
            TupleLinkedList list;

            ////if (keyValue == null) System.Diagnostics.Debugger.Break();
            ////if (GetTupleView<TKey>() == null) System.Diagnostics.Debugger.Break();
            return GetTupleView<TKey>().TryGetValue(keyValue, out list) ? list : null;
        }

        /// <summary>
        /// Returns a list of tuple lists that do not match a given key value.
        /// </summary>
        /// <param name="tupleIndexer">The tuple indexer used for evaluation.</param>
        /// <param name="keyValue">A key value used for match.</param>
        /// <typeparam name="TKey">The key type.  This must be an IComparable type.</typeparam>
        /// <returns>
        /// A list of tuple lists contains tuples that do not match a given key value.
        /// </returns>
        public IList<TupleLinkedList> GetNotTupleLists<TKey>(TupleIndexer<TTuple> tupleIndexer, TKey keyValue) where TKey : IComparable
        {
            var tupleLinkedListOut = new List<TupleLinkedList>();

            if (tupleIndexer == null)
            {
                return tupleLinkedListOut;
            }

            var tupleView = GetTupleView<TKey>(tupleIndexer);

            if (tupleView == null)
            {
                return tupleLinkedListOut;
            }

            var list = default(TupleLinkedList);

            foreach (var key in tupleView.Keys)
            {
                if (0 != key.CompareTo(keyValue))
                {
                    continue;
                }

                list = tupleView[key];
                break;
            }

            if (list == null)
            {
                return tupleLinkedListOut;
            }

            // foreach (HashSet<Tuple> hsOut in tupleView.Values)
            foreach (var tupleViewEntry in tupleView)
            {
                var castKeyValue = keyValue as ITupleRelationIndex;

                if (castKeyValue == null)
                {
                    if (tupleViewEntry.Value != list)
                    {
                        tupleLinkedListOut.Add(tupleViewEntry.Value);
                    }
                }
                else
                {
                    if (castKeyValue.Filter(list, tupleViewEntry))
                    {
                        tupleLinkedListOut.Add(tupleViewEntry.Value);
                    }
                }
            }

            return tupleLinkedListOut;
        }

        /// <summary>
        /// Registers a tuple indexer with the Tuple Relation.
        /// </summary>
        /// <param name="tupleIndexer">The tuple indexer to be registered.</param>
        /// <typeparam name="TKey">The key type.</typeparam>
        public void RegisterIndexer<TKey>(TupleIndexer<TTuple> tupleIndexer)
        {
            if (tupleIndexer == null)
            {
                return;
            }

            this.tupleIndexers.Add(tupleIndexer);
            this.tupleRelation.Add(tupleIndexer, new Dictionary<TKey, TupleLinkedList>());
        }

        /// <summary>
        /// Registers an 'all' indexer with the tuple relation.
        /// </summary>
        /// <param name="tupleIndexer">The 'all' indexer to be registered.</param>
        public void RegisterAllIndexer(TupleIndexer<TTuple> tupleIndexer)
        {
            if (tupleIndexer == null)
            {
                return;
            }

            this.addAll = true;
            this.evaluateAll = tupleIndexer;
            var tupleView = (Dictionary<Type, TupleLinkedList>)this.evaluateAll.TupleViewFactory();
            var tupleList = TupleLinkedListPool.GetItem(tupleIndexer.Id);
            tupleList.TupleRelationId = this.Id;
            tupleView.Add(typeof(All), tupleList);
            this.tupleRelation.Add(this.evaluateAll, tupleView);
        }

        /// <summary>
        /// Adds a tuple to a tuple relation using all registered evaluators to select the
        /// tuple lists and append the tuple list to them.
        /// </summary>
        /// <param name="tuple">The tuple to be added to the tuple view.</param>
        public void AddTuple(Tuple tuple)
        {
            Debug.Assert(tuple != null, "The tuple cannot be null");

            if (this.tupleIndexers.Count > 0)
            {
                this.DoAddTuple(tuple);
            }

            if (!this.addAll)
            {
                return;
            }

            var tupleView = this.GetTupleView(this.evaluateAll);
            var tupleList = (TupleLinkedList)tupleView[AllKey];

            tupleList.Add(tuple);
            tuple.RegisterTupleList(tupleList);
        }

        /// <summary>
        /// Processes a tuple and adds or amends a quantative tuple in a tuple relation using 
        /// all registered evaluators to select the tuple lists and append the tuple list to them.   
        /// </summary>
        /// <param name="tuple">The tuple to be processed.</param>
        /// <param name="quantificationTupleType">Quantification tuple to which this tuple will be map.</param>
        /// <returns>Boolean value indicating if the tuple should be re-asserted.</returns>
        public bool AddTuple(Tuple tuple, Type quantificationTupleType)
        {
            Debug.Assert((this.tupleIndexers.Count > 0), "There are no registered indexers.");

            if (tuple == null || quantificationTupleType == null)
            {
                TupleStoreExceptionHelper.ThrowTupleStoreExAddNullTuple();
                return false;
            }

            var returnVal = false;

            foreach (var tupleIndexer in this.tupleIndexers)
            {
                var tupleView = this.GetTupleView(tupleIndexer);
                var currentTupleList = default(TupleLinkedList);

                // Evaluate using each nested evaluator 
                foreach (var nestedTupleIndexer in tupleIndexer)
                {
#if MONO201_COMPLIANT
                    var keyValue = nestedTupleIndexer.Evaluator(tuple as TTuple) ?? new Null();
#else
                    var keyValue = nestedTupleIndexer.Evaluator((TTuple)tuple) ?? new Null();
#endif

                    if (tupleView == null)
                    {
                        // We can assume that currentTupleList is not null here
                        tupleView = nestedTupleIndexer.TupleViewFactory();
                        currentTupleList.NestedTupleView = tupleView;
                    }

                    TupleLinkedList tupleList;

                    if (tupleView.Contains(keyValue))
                    {
                        tupleList = (TupleLinkedList)tupleView[keyValue];
                    }
                    else
                    {
                        // Not found, so create
                        Qtuple quantificationTuple;

                        if (typeof(UniversalQtuple) == quantificationTupleType)
                        {
                            quantificationTuple = new UniversalQtuple();
                        }
                        else
                        {
                            if (typeof(ExistentialQtuple) == quantificationTupleType)
                            {
                                quantificationTuple = new ExistentialQtuple();
                            }
                            else
                            {
                                throw new ArgumentException(
                                    Properties.Resources.ExTuplesTupleRelationIncorrectQType,
                                    Properties.Resources.ArgQuantificationTupleType);
                            }
                        }

                        tupleList = TupleLinkedListPool.GetItem(nestedTupleIndexer.Id);
                        tupleList.TupleRelationId = this.Id;
                        tupleView.Add(keyValue, tupleList);

                        currentTupleList = tupleList;

                        // Only store tuple and register tuple list if indexer indicates this.
                        if (tupleIndexer.StoreTupleList)
                        {
                            // Although we key on the value returned by evaluating the fact tuple, we
                            // store the Qtuple as a singleton in the list.   Effectively, we are 
                            // mapping all fact tuples for a given key to a single Qtuple.
                            quantificationTuple.RegisterTupleList(currentTupleList);
                            tupleList.Add(quantificationTuple);
                        }
                    }

                    tupleView = currentTupleList.NestedTupleView;

                    // The tuple list sould contain a single qTuple
                    Debug.Assert((tupleList.Count == 1), "The quantification tuple list does not contain a singleton Qtuple.");

                    // This loop will be executed just once.
                    Qtuple quantificationTupleObj;
                    while ((quantificationTupleObj = (Qtuple)tupleList.Next()) != null)
                    {
                        // Increment the match count
                        quantificationTupleObj.MatchCount++;

                        // Decide if the tuple should be re-asserted here.
                        if (quantificationTupleObj is UniversalQtuple)
                        {
                            // TODO: There may be further ways we can optimise the code here to only reassert
                            // on a threshold value
                            returnVal = true;
                        }
                        else
                        {
                            // We can assume this is an existential tuple.  NB., this means the Qtuple class
                            // is an existential tuple by default
                            if (!returnVal)
                            {
                                returnVal = quantificationTupleObj.MatchCount == 1;
                            }
                        }
                    }
                }
            }

            return returnVal;
        }

        /// <summary>
        /// Removes a tuple from a tuple view.
        /// </summary>
        /// <param name="tuple">The tuple to be added to the tuple view.</param>
        public void RemoveTuple(Tuple tuple)
        {
            Debug.Assert((this.tupleIndexers.Count > 0), "There are no registered evaluators.");

            if (tuple != null)
            {
                foreach (var tupleIndexer in this.tupleIndexers)
                {
                    var tupleView = this.GetTupleView(tupleIndexer);
                    TupleLinkedList currentTupleList;

                    // Evaluate using each nested evaluator 
                    foreach (var nestedTupleIndexer in tupleIndexer)
                    {
#if MONO201_COMPLIANT
                        var keyValue = nestedTupleIndexer.Evaluator(tuple as TTuple) ?? new Null();
#else
                        var keyValue = nestedTupleIndexer.Evaluator((TTuple)tuple) ?? new Null();
#endif

                        TupleLinkedList tupleList;

                        if (tupleView.Contains(keyValue))
                        {
                            tupleList = (TupleLinkedList)tupleView[keyValue];
                        }
                        else
                        {
                            tupleList = TupleLinkedListPool.GetItem(nestedTupleIndexer.Id);
                            tupleList.TupleRelationId = this.Id;
                            tupleView.Add(keyValue, tupleList);
                        }

                        currentTupleList = tupleList;
                        currentTupleList.Remove(tuple);

                        tupleView = currentTupleList.NestedTupleView;
                    }
                }
            }

            // Remove from 'all' list, if used
            if (!this.addAll)
            {
                return;
            }

            var tupleViewAll = this.GetTupleView(this.evaluateAll);

#if MONO201_COMPLIANT
                ((TupleLinkedList)tupleViewAll[TupleRelation<TTuple>.allKey]).Remove(tuple);
#else
            ((TupleLinkedList)tupleViewAll[AllKey]).Remove(tuple);
#endif
        }

        /// <summary>
        /// Clears all Tuples and indexes from the Tuple Relation.
        /// </summary>
        public void ClearAllTuples()
        {
            foreach (var ts in this.tupleRelation.Values)
            {
                this.ClearTupleViews(ts);
            }
        }

        /// <summary>
        /// Adds a tuple to a tuple relation using all registered tuple indexers to select the
        /// tuple lists and append the tuple list to them.
        /// </summary>
        /// <param name="tuple">The tuple to be added to the tuple view.</param>
        private void DoAddTuple(Tuple tuple)
        {
            Debug.Assert((this.tupleIndexers.Count > 0), "There are no registered indexers.");
            Debug.Assert(tuple != null, "The tuple cannot be null");

            foreach (var tupleIndexer in this.tupleIndexers)
            {
                var tupleView = this.GetTupleView(tupleIndexer);

                var currentTupleList = default(TupleLinkedList);

                // Evaluate using each nested evaluator 
                foreach (var nestedTupleIndexer in tupleIndexer)
                {
#if MONO201_COMPLIANT
                    var keyValue = nestedTupleIndexer.Evaluator(tuple as TTuple) ?? new Null();
#else
                    var keyValue = nestedTupleIndexer.Evaluator((TTuple)tuple) ?? new Null();
#endif

                    if (tupleView == null)
                    {
                        // We can assume that currentTupleList is not null here
                        tupleView = nestedTupleIndexer.TupleViewFactory();
                        currentTupleList.NestedTupleView = tupleView;
                    }

                    TupleLinkedList tupleList;

                    if (tupleView.Contains(keyValue))
                    {
                        tupleList = (TupleLinkedList)tupleView[keyValue];
                    }
                    else
                    {
                        tupleList = TupleLinkedListPool.GetItem(nestedTupleIndexer.Id);
                        tupleList.TupleRelationId = this.Id;
                        tupleView.Add(keyValue, tupleList);
                    }

                    currentTupleList = tupleList;

                    // Only store tuple and register tuple list if indexer indicates this.
                    if (tupleIndexer.StoreTupleList)
                    {
                        tuple.RegisterTupleList(currentTupleList);
                        currentTupleList.Add(tuple);
                    }

                    tupleView = currentTupleList.NestedTupleView;
                }
            }
        }

        /// <summary>
        /// Recursively clears all nested tuple views.
        /// </summary>
        /// <param name="ts">A collection of tuple lists.</param>
        private void ClearTupleViews(IDictionary ts)
        {
            if (ts == null)
            {
                return;
            }

            foreach (TupleLinkedList tll in ts.Values)
            {
                this.ClearTupleViews(tll.NestedTupleView);
                TupleLinkedListPool.ReturnItem(tll);
            }
        }
    }
}
